
// 基本使用
function start() {
  let name: string = 'hello world'
  console.log(name)
}
start()

// 一. 静态类型 -----------------------------------------------------------------
let count: number = 111
// count = '3' // 不能将类型“string”分配给类型“number”
count = 222

interface objType {
  name: string,
  age: number
}

const zhangsan: objType = {
  name: '张三',
  // 如果不写完整，类型 "{ name: string; }" 中缺少属性 "age"，但类型 "objType" 中需要该属性。
  age: 18
}
console.log(zhangsan.name)


// 基础类型 number string null undefined boolean void never symbol any unknown
const numType: number = 233
const stringType: string = '字符串'
const is: boolean = false
// null类型可以赋值给undefined  null == undefined 反之亦然
const value: null = null
const value1: undefined = null
const value2: null = undefined
// void 在js中不管怎么执行 void 方法 都是返回undefined
// typescipt 中代表无效的 告诉这个函数没有返回值
function fn(): void {} // 正确
function testFn(): void {
  console.log('123')
  // return 123 // 报错，能执行 但不接受返回值存在
}
// never 一个永远不会有值的类型 也可以说 永远执行不完的类型 代表不会有值
// undefined、null也算是有值 一般不会用到
// const test: never = null // 错误
// const test: undefined = null // 错误
function Fn(): never { // 正确 因为死循环了
  while(true) {}
}
function Fn1(): never { // 正确 因为递归 永远没有出口
  Fn1()
}
function Fn2(): never { // 正确 代码报错 执行不下去
  throw new Error()
  console.log(123) // 后面的代码永远也执行不到
}
// any 代表任何类型 在项目中不要大片定义 any 类型，不要滥用
let val: any = 'hello world'
val = 233
val = null
val = { name: 'zhangsan' }
val = function() {
  return '任何类型的值'
}
// unknown 意为未知的，是 TS 的第二个 any 类型 也是接受任何类型 
let val1: unknown = 'hello world'
val1 = 233
val1 = null
val1 = { name: 'zhangsan' }
val1 = function() {
  return '任何类型的值'
}
// any 和 unknown 的区别
let valueAny: any = ''
let valueUnknown: unknown = ''
valueAny = '任何类型'
valueUnknown = '未知类型'

let valueNull: null = null
// valueNull = valueAny // 赋值成功
// valueNull = valueUnknown // 赋值失败

// 区别：any是任何的、任意的，unknown是未知的。
// 所以给unknown类型赋值任何类型都没关系，因为本来就是未知的
// 但当未知的类型被赋值类型的时候就确定为了该类型，所以当再给其赋值其他类型的时候就不会接受，会报错
// unknown类型能接受其他类型，但其他类型不能接受unknown类型




// 二. 对象类型 对象 数组 函数 类 -----------------------------------------------------------------
const objType: {
  // 键的类型
} = {
  // 值
}

const liet: [] = [ ] // 数组
const liet1: number [] = [ 233, null ] // 数字类型的数组
const liet2: string [] = [ '233', '556' ] // 字符串类型的数组
const liet3: object [] = [ null, {} ] // 对象类型的数组
const liet4: Array<number> = [ 1, 2, null ] // 泛型定义数组是数字类型

class Person{}
const lisi: Person = new Person()

const func: () => string = () => { return '必须返回字符串的函数' }

// 三. type annotation 类型注解 -----------------------------------------------------------------
// type inference 类型推断

let annotation: string = '类型注解'
annotation = "这个 : string 意思就是类型注解"

// 不进行类型注解 ts能自动分析变量的类型 进行注解 ——这就是类型推断
let stringInference = '字符串类型'


// 不需要类型注解的例子
let one = 1
let two = 2
let three = one + two
// 需要的例子
function getTotal(one, two) { // 参数 "one" 隐式具有 "any" 类型，但可以从用法中推断出更好的类型
  return one + two
}

// 函数显示注解和函数参数不会类型推导问题
// 1. 函数返回类型为number
function fn3(a, b): number {
  return a + b;
}
fn3(1, 2)
// 2. 函数void  显示注解为void类型，函数没有返回值
function fn4(): void {
  console.log(1)
}
// 3. 函数不会自动类型推导
function testFnQ(a, b) {
  return a + b
}
testFnQ(1,2)
// 相当于                      第三个 any 意思是返回值是any类型
function testFnQ2(a: any, b:any): any {
  return a + b
}
testFnQ2(1,2)
// 不会自动类型推导，我们实参虽然传入的1和2，但是形参方面是可以接受任意类型值的，
// 所以系统也识别不出来你传递的什么，所以这里得需要我们显示定义注解类型
// 正确写法
function testFnQ3(a:number, b:number) {
  return a + b
}
testFnQ3(1,2)
// ！！！工作中（潜规则）: 能推断出来的类型可以不写，不能推断出来的一定要写注解类型


// 函数参数为对象的类型注解
function testFnQ4(obj : {num: number}) {
  return obj.num
}
testFnQ4({num: 18})
function testFnQ5({a , b} : {a: number, b: number}) {
  return a + b
}
testFnQ5({a: 18, b: 233})

// 数组的类型注解
const numberArr: number[] = [ 1, 2, 3 ] // 简单类型
const stringArr: string[] = [ '1', '2', '3' ]
const undefinedArr: undefined[] = [ undefined, undefined ]

const doubleArr: (number | string)[] = [ 1, '2', 3 ] // 多类型


// 四. 元组 Tuple -----------------------------------------------------------------
// 这样定义缺陷：数组里的值改变位置，没报错，但业务里面可能会报错
const numArr2: (number | string)[] = ['名字', 18] 
const numArr3: (number | string)[] = [18, '名字'] 
// 表示一个已知数组的数量和类型的数组，定义数组中每一个值的类型，一般不经常使用
const numArr: [number, string, string] = [1, '123', '465']
// const numArr2: [string, string] = [1, '123'] // 数字1 报错
// CSV 格式   已经过时的，不用的
const doubleStuArr: [string, number, string][] = [
  ['123', 1, '321'],
  ['456', 1, '654'],
  ['789', 1, '987']
]

// 五. 枚举enum -----------------------------------------------------------------
// 可以设置默认值，如果不设置则为索引。可以递增值，也可以设置默认值
enum color {
  RED,
  BLUE = "blue",
  GREEN = "green"
}

// color["RED"] 0
// color["BLUE"] blue
// color["GREEN"] green
enum color2 {
  RED,	   // 0
  BLUE = 4,  // 4
  GREEN      // 5
}

// 像上面的color中RED没有设置值，那么它的值则为0，如果BLUE也不设置的话那么它的值则是1，
// 如果设置值则是返回设置的值，不设置则是递增。

// 注意点 enum没有json对象那样灵活，enum不能在任意字段上设置默认值
// enum color { // 报错
//   RED,
//   BLUE = "blue",
//   GREEN
// }

// RED没有设置值，然后BLUE设置了默认值，但是GREEN又没有设置，这时这个GREEN会报错。
// 因为你第二个BLUE设置完默认值，第三又不设置，这时代码都不知道该咋递增了，所以报错
// 像上面第二个例子 给BLUE设置一个数字值，这时第三个GREEN不设置也会跟着递增，因为都是number类型。


// 六. type alias 类型别名 -----------------------------------------------------------------
// 简单版
// const objArr: {name: string, age: number}[] = [
//   {name: 'haha', age: 12},
//   {name: 'hehe', age: 15}
// ]

type student = {name: string, age: number} // 类型别名
class stu { // 或是使用类 class，但需注意 每个后面必须要带 ;
  name: string;
  age: number;
}

const objArr: student[] = [
  {name: 'haha', age: 12},
  {name: 'hehe', age: 15}
]
const obj1Arr: stu[] = [
  {name: 'haha', age: 12},
  {name: 'hehe', age: 15}
]

// 七. 接口Interface -----------------------------------------------------------------
// 就是方便我们定义一处代码，多处复用。接口里面也存在一些修饰符
const testObj: { name: string, age: number } = { name: "呵呵", age: 18 }
const testObj1: { name: string, age: number } = { name: "哈哈", age: 18 }
// 1.使用 interface 定义实现复用
interface Types {
  name: string, 
  age: number
}
const testObj2: Types = { name: "呵呵", age: 18 }
const testObj3: Types = { name: "哈哈", age: 18 }

// 2. readonly修饰符
interface TypesReadonly {
  readonly name: string, 
  readonly age: number
}
const testObj4: TypesReadonly = { name: "jack", age: 18 }
const testObj5: TypesReadonly = { name: "jam", age: 18 }
// testObj4.name = "张三" // 无法更改name属性，因为它是只读属性
// testObj5.name = "李四" // 无法更改name属性，因为它是只读属性

// 3. ?可选修饰符
// 如果不写可选修饰符，那interface里面的属性都是必填的
interface Typeschoic {
  readonly a: string, 
  readonly b: number,
  sex?: string
}
const testObjchoic: Typeschoic = { a: "hi", b: 18}

// 4. extends继承
// interface也是可以继承的，跟ES6Class类一样，使用extends关键字
interface TypesExtends {
  readonly name: string, 
  readonly age: number,
  sex?: string
}
interface ChildrenType extends TypesExtends { // 这ChildrenType接口就已经继承了父级Types接口
  hobby: string[]
}
const testObjExtend: ChildrenType = { name: "jack", age: 18, hobby: ["code", "羽毛球"] }

// 5. propName扩展
// 可以写入不在interface里面的属性
// interface Types {
//   readonly name: string, 
//   readonly age: number,
//   sex?: string,
// }
// const testObj: Types = { name: "jack", age: 19, hobby: [] }

// 上面这个testObj这行代码会报错，因为hobby属性不存在interface接口中
interface TypesPropName {
  readonly name: string, 
  readonly age: number,
  sex?: string,
  [propName: string]: any // propName字段必须是 string类型 or number类型。 值是any类型，也就是任意的
}
const testObjPropName: TypesPropName = { name: "jack", age: 19, hobby: [] } 

// 注意点
// interface Types1 {
//   name: string
// }

// interface Types1 {
//   // name: number
//   name: string
// }
// 上面两个同名称的interface接口，里面的属性也是同名称，但是类型不一样。这第二个的Types1就会爆红
// 注：后续声明的接口，必须跟前面声明的同名属性类型必须保持一致！！！


// 八. type类型别名和interface接口的区别 -----------------------------------------------------------------
// 1. type不支持interface声明
type Types0 = number
// type Types = string // 报错， 类型别名type不允许出现重复名字

interface Types1 {
  name: string
}
interface Types1 {
  age: number
}
// 会得到 { name：string, age: number }
// interface接口可以出现重复类型名称，如果重复出现则是，合并

// 2. type支持表达式 interface不支持
const count1: number = 123
type testType = typeof count1

// interface testType1 {
//   [name: typeof count1]: any // 报错
// }

// 3. type 支持类型映射，interface不支持
type keys = "name" | "age"
type KeysObj = {
  [propName in keys]: string
}

const PersonObj: KeysObj = { // 正常运行
  name: "jack",
  age: "18"
}

// interface testType1 {
//   [propName in keys]: string // 报错
// }


// 九 联合类型 -----------------------------------------------------------------
// 联合类型用 | 表示，说白了就是满足其中的一个类型就可以
const statusTest: string | number = "jack"
const flag: boolean | number = true

// 注意，联合类型不能用于函数参数
function testStatusFn(params: number | string) {
  // 报错 类型“string | number”上不存在属性“toFixed”。
  // 类型“string”上不存在属性“toFixed”。
  // console.log(params.toFixed())
}
testStatusFn(1)
// 函数参数类型不能类型自动推导，更何况现在用上联合类型，系统更懵逼了，不能识别当前实参的类型。

// 类型保护
// 1. typeof
function testStatusFn1(params: string | number) {
  if (typeof params == "string") {
    console.log(params.split)
  }

  if (typeof params == "number") {
    console.log(params.toFixed)
  }
}
testStatusFn1(1)

// 2. in
// 报错
interface frontEnd {
  name: string
}
interface backEnd {
  age: string
}

function testStatusFn2(params: frontEnd | backEnd) {
  // console.log(params.name) // 报错不存在
}
testStatusFn2({name: "jack"})
// 使用in
function testStatusFn3(params: frontEnd | backEnd) {
  if ("name" in params) {
    console.log(params.name)
  }
  if ("age" in params) {
    console.log(params.age)
  }
}
testStatusFn3({name: "jack"})

// 3. as 断言
// 使输出的值只能属于某个定义的类型里的
function testStatusFn4(params: frontEnd | backEnd) {
  if ("name" in params) {
    const res = (params as frontEnd).name
    // const res = (params as frontEnd).age // 报错 类型“frontEnd”上不存在属性“age”
    console.log(res)
  }
  if ("age" in params) {
    const res = (params as backEnd).age
    console.log(res)
  }
}
testStatusFn4({name: "jack"})


// 十. 交叉类型 -----------------------------------------------------------------
// 交叉类型就是跟联合类型相反，它用&表示，交叉类型就是两个类型必须存在
interface frontEnd1 {
  name: string
}

interface backEnd1 {
  age: number
}

function testStatusFn5(params: frontEnd1 & backEnd1) {}

testStatusFn5({age: 118, name: "jack"})

// 实参必须传入两个**接口(interface)**全部的属性值才可以

// 注意 两个接口类型中都出现了同名属性，但是类型不一样，这时类型就会变为never
interface frontEnd {
  name: string
}

interface backEnd {
  name: number
}

function testStatusFn6(params: frontEnd & backEnd) {
  console.log(params)
}

// testStatusFn6({name: "jack"}) // 不能将类型“string”分配给类型“never”


// 十一. 泛型 -----------------------------------------------------------------
// 泛型是专门针对不确定的类型使用，并且灵活
// 泛型的使用大部分都是使用<T>，当然也可以随便使用，如：<Test>、<Custom>都可以

// 老例子
function test(a: string | number, b: string | number) {
  console.log(a, b)
}
test(1, "泛型")
// 比如上面，函数参数注解类型定义string和number 
// 但要是有个需求，就是 实参 必须传入同样的类型（传入两个number类型），也叫联合类型
// 但是如果要在加一个boolean类型，那么联合类型还得在追加一个boolean，那这样代码太冗余了。

// 使用泛型
function test1<T>(a: T, b: T) {
  console.log(a, b)
}
// 调用后面跟着尖括号这就是泛型的类型，这时报错，因为在调用的使用类型是number，所以只能传入相同类型的
// test1<number>(1, "hello")
test1<boolean>(true, false) 
test1<string>("hello", "哈哈")

// 上面这使用泛型就解决了我们刚才说的传入同一个类型参数问题
// 但是泛型也可以使用不同的参数，可以把调用类型定义为<any> 任何类型
function test2<T>(a: T, b:T) {
  console.log(a, b)
}
test2<any>('哈哈', {'num': 123})

// extends 类型约束
// 如果只希望传入特定类型如 number类型和string类型 泛型也给我们提供了约束类型 只能选择string、number
function test3<T extends number | string, Y extends number | string >(a: T, b:Y) {
  console.log(a, b)
}
test3<number, string>(123, '哈哈')


// 十二. 类 -----------------------------------------------------------------
// 类 分为类的内部 和 类的外部
// 以下这前三个修饰符是在TypeScript类中才能使用，在JavaScript类中是不支持的。
// 1. public 默认定义的属性及方法
// public为类的公共属性，就是不管在类的内部还是外部，都可以访问该类中属性及方法。

// 2. private 类的私有属性
// 在{}外面是不能访问private定义的属性及方法的，只有在当前类里面才能访问，当前类就是{}里面区域内。

// 3. protected 类的保护属性，
// 只有在当前类和子类可以访问。也就是说用protected属性定义的子类也可以访问。

class Person1 {
  public group = '高二'
  private name = "张三";
  protected age = 18;
}
const res = new Person1();
res.group = '高三'
console.log(res.group) // public 内部、外部都能修改
// 下面会报错，name 为私有属性，只能在类内部访问 age 为受保护熟悉 只能在类内部 或 子类里面访问
// console.log(res.name, res.age)

class Scholl extends Person1 {
  getData() {
    // console.log(this.name) // 属性“name”为私有属性，只能在类“Person1”中访问。
    return this.age
  }
}
const temp = new Scholl()
console.log(temp.getData()) // 张三，18。可以正常访问父类的属性

// 4. implements 实现一个新的类
// 只能在class中使用，从父级或者从接口实现所有的属性和方法 不实现则会报错
interface implementClass {
  name: string,
  fn: () => void
}

class newClass implements implementClass {
  name: string
  fn: () => void
}

// 5. constructor 类的构造函数 以及 super 关键词
class Person3 {
  // 全写法
  // public name: string
  // constructor(name: string) {
  //   this.name = name
  // }
  // 简化写法
  // constructor(public name: string) {}
  // 就算不写 constructor 也会自带一个默认的 constructor
  constructor() {}
}

class Teacher extends Person3 {
  constructor(public age: number) {
    // 当如果子类里面也要写一个构造函数时 使用 super
    // super('张三') // super 相当于 constructor Person3(name: string): Person3
    // 注意，不管父类有没有写 constructor 子类要写 constructor 时都要加上 super()
    super()
  }
}

// const person = new Person3('张三') // 需要为其提供一个自变量
// console.log(person.name) // 张三
const person1 = new Teacher(18) // 需要为其提供一个自变量
console.log(person1.age) // 张三


// 6. readonly 只读属性
class Person4 {
  public readonly _name : string
  constructor(name: string) {
    this._name = name
  }
}
const person = new Person4('张三') // 需要为其提供一个自变量
// person._name = '李四' // 报错 无法分配到 "_name" ，因为它是只读属性。
console.log(person._name) // 张三

// 7. 类的 Getter Setter 方法
class Person5 {
  constructor(private _age: number) {}
  // 因为 private 私有属性 在外部访问会报错
  get age() { // 使用 getter 方法 相当于重新赋值，就可以在外部访问
    return this._age - 2
  }
  set age(age: number) { // 使用 setter 方法 也可以重新赋值
    this._age = age + 6
  }
}
const person2 = new Person5(18) // 需要为其提供一个自变量
person2.age = 18
console.log(person2.age)

// 8. static 静态属性
class Person6 {
  // study() {
  //   return '在学ts'
  // }
  // 使用 static 可以直接输出
  static study2() {
    return '在学ts'
  }
}
// const person3 = new Person6() // 需要实例化才能输出
// console.log(person3.study())
console.log(Person6.study2())


// 9. abstract 抽象类
// 抽象类里面方法是抽象的，那么本身的类也必须是抽象的
abstract class Student {
  abstract skill() // 没有大括号(不能写函数体)，abstract抽象方法不能实例化
}
// 类里面有抽象方法，那么子类也必须要重新该方法。
class A extends Student {
  skill() {
    console.log('会JS')
  }
}

class B extends Student {
  skill() {
    console.log('会TS')
  }
}



// 十三. 模块 -----------------------------------------------------------------
// TypeScript也支持import和export
// 导入
// import xxx, { xxx } from "./xxx"
// 导出
// export default {}
// export const name = "模块导出"



// 十四. namespace 命名空间 -----------------------------------------------------------------
// 项目中文件是不能有重复的变量(不管是不是一样的文件还是其它文件)，否则就直接爆红了
// 命名空间一个最明确的目的就是解决重名问题。
namespace Component { // 使用 命名空间 namespace 关键字命名 Component 
  const q = {}

  export interface obj {
    name: string
  }
}
// 可以看到变量q没有写export关键字，这证明它是内部的变量，就算别的.ts文件引用它，
// 它也不会暴露出去。而interface这个obj接口是可以被全局访问的。


// 十五. tsConfig.json -----------------------------------------------------------------
// tsconfig文件，是ts的编译配置文件，作用是将ts文件编译成js文件。
// tsc -init这个命令会生成该文件出来。
// 执行完该命令，可以看到根目录下会生成一个tsconfig.json文件，里面有一堆属性。
// 直接执行tsc命令可以将根目录下所有的.ts文件全部编译成.js文件输出到项目下。


// 搭建浏览器开发环境的步骤
// 1.建立好文件夹后，打开 VSCode，把文件夹拉到编辑器当中，然后打开终端，运行npm init -y,创建package.json文件。

// 2.生成文件后，我们接着在终端中运行tsc -init,生成tsconfig.json文件。

// 3.新建src和build文件夹，再建一个index.html文件。

// 4.在src目录下，新建一个page.ts文件，这就是我们要编写的ts文件了。

// 5.配置tsconfig.json文件，设置outDir和rootDir(在 15 行左右)，也就是设置需要编译的文件目录，和编译好的文件目录。

// 6.然后编写index.html，引入<script src="./build/page.js"></script>,当让我们现在还没有page.js文件。

// 7.编写page.ts文件，加入一句输出console.log('jspang.com'),再在控制台输入tsc,就会生成page.js文件

// 8.再到浏览器中查看index.html文件，如果按F12可以看到jspang.com，说明我们的搭建正常了。

